package com.weiss.jpro.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock {

    private String name; // 业务的名称，锁的名称
    private static final String KEY_PREFIX = "lock:"; // 锁的前缀
    // 同一个进程的UUID一样(通一个常量池)，但是线程id不一样，这样就可以保证拼接起来是唯一的了
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; // isSimple去掉UUID下划线，"-"用于拼接后面的线程号
    private StringRedisTemplate stringRedisTemplate;
    // 查看类层级：ctrl+h

    // 使用的是静态常量和静态代码块，这个类一加载，脚本就初始化完成了
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    // 由于UNLOCK_SCRIPT是静态的，在静态代码块进行初始化
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        // 设置脚本位置
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); // 使用spring提供的ClassPathResource寻找classpath下的资源
        UNLOCK_SCRIPT.setResultType(Long.class); // 返回值类型
    }

    // 通过构造函数接收参数 alt+insert
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        // 获取线程标识
        long threadId = Thread.currentThread().getId();
//        String value = threadId + ""; // 需要传入string类型，上面是long
        String value = ID_PREFIX + threadId;
        // 获取锁 通过setnx操作
        String key = KEY_PREFIX + name; // key
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);
        // redis操作返回的是包装类型Boolean，函数标识为boolean类型，
        // 自动拆箱有可能有安全风险，如success为null，拆箱就会返回空指针
        // 通过Boolean.TRUE.equals(success)进行比较，Boolean.TURE是常量，
        // success为true，返回true,为false返回false，为null也返回false,就可以避免空指针的风险
        return Boolean.TRUE.equals(success);
    }

    // ctrl+i 实现方法
    @Override
    public void unlock() {
        // 调用lua脚本
        // RedisTemplate.execute()传入的是RedisScript类，这个类需要去加载lua脚本文件，
        // 每次释放锁的时候去读取文件会产生io流，性能肯定不好，所以应该提前读取脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                // 传入的参数是一个集合，使用Collections.singletonList()传入
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId()
                );
    }

    //    @Override
//    public void unlock() {
//        // 获取线程标识
//        String value = ID_PREFIX + Thread.currentThread().getId();
//        // 获取锁中的标识
//        String key = KEY_PREFIX + name;
//        String lockValue = stringRedisTemplate.opsForValue().get(key);
//        // 判断是否一致
//        if (value.equals(lockValue)) {
//            // 释放锁，直接删除
////            String key = KEY_PREFIX + name;
//            stringRedisTemplate.delete(key);
//        }
//    }
}
